#import "UIDevice_SH.h"
#import "UIScreen_SH.h"

#import <sys/sysctl.h>
#import <sys/utsname.h>

#import <arpa/inet.h>
#import <ifaddrs.h>

@implementation UIDevice (SH)

+ (NSString *)identifierForVendorString
{
    static NSString *      str = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      str = self.currentDevice.identifierForVendor.UUIDString ?: @"";
    });
    return str;
}

+ (NSString *)UUIDString
{
    static NSString *      s = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      s = self.identifierForVendorString;
      if (s.length < 1) {
          s = @"null";
      }
    });
    return s;
}

+ (NSString *)language
{
    return NSLocale.preferredLanguages.firstObject ?: @"";
}

+ (NSString *)systemVersion
{
    static NSString *      s = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      s = self.currentDevice.systemVersion;
    });
    return s;
}

+ (CGFloat)systemVersionF
{
    static CGFloat         s;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      s = self.currentDevice.systemVersion.floatValue;
    });
    return s;
}

+ (NSString *)systemName
{
    static NSString *      s = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      s = self.currentDevice.systemName;
    });
    return s;
}

+ (NSString *)deviceModel
{
    static NSString *      s = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      struct utsname systemInfo;
      uname(&systemInfo);
      NSString *platform = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];

      if ([@[ @"i386", @"x86_64" ] containsObject:platform]) {
          s = @"Simulator";
      } else {
          s = platform;
      }
    });
    return s;
}

+ (NSString *)deviceType
{
    static NSString *      s = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      struct utsname systemInfo;
      uname(&systemInfo);
      NSString *deviceString = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];

      if ([@[ @"iPhone3,1", @"iPhone3,2", @"iPhone3,3" ] containsObject:deviceString]) {
          s = @"iPhone 4";
      } else if ([deviceString isEqualToString:@"iPhone4,1"]) {
          s = @"iPhone 4S";
      } else if ([@[ @"iPhone5,1", @"iPhone5,2" ] containsObject:deviceString]) {
          s = @"iPhone 5";
      } else if ([@[ @"iPhone5,3", @"iPhone5,4" ] containsObject:deviceString]) {
          s = @"iPhone 5c";
      } else if ([@[ @"iPhone6,1", @"iPhone6,2" ] containsObject:deviceString]) {
          s = @"iPhone 5s";
      } else if ([deviceString isEqualToString:@"iPhone7,1"]) {
          s = @"iPhone 6 Plus";
      } else if ([deviceString isEqualToString:@"iPhone7,2"]) {
          s = @"iPhone 6";
      } else if ([deviceString isEqualToString:@"iPhone8,1"]) {
          s = @"iPhone 6s";
      } else if ([deviceString isEqualToString:@"iPhone8,2"]) {
          s = @"iPhone 6s Plus";
      } else if ([deviceString isEqualToString:@"iPhone8,4"]) {
          s = @"iPhone SE";
      } else if ([deviceString isEqualToString:@"iPhone9,1"]) {
          s = @"iPhone 7";
      } else if ([deviceString isEqualToString:@"iPhone9,2"]) {
          s = @"iPhone 7 Plus";
      } else if ([deviceString isEqualToString:@"iPhone9,3"]) {
          s = @"iPhone 7";
      } else if ([deviceString isEqualToString:@"iPhone9,4"]) {
          s = @"iPhone 7 Plus";
      } else if ([@[ @"iPhone10,1", @"iPhone10,4" ] containsObject:deviceString]) {
          s = @"iPhone 8";
      } else if ([@[ @"iPhone10,2", @"iPhone10,5" ] containsObject:deviceString]) {
          s = @"iPhone 8 Plus";
      } else if ([@[ @"iPhone10,3", @"iPhone10,6" ] containsObject:deviceString]) {
          s = @"iPhone X";
      } else if ([deviceString hasPrefix:@"iPod"]) {
          s = @"iPod Touch";
      } else if ([deviceString isEqualToString:@"iPad1,1"]) {
          s = @"iPad";
      } else if ([deviceString isEqualToString:@"iPad1,2"]) {
          s = @"iPad";
      } else if ([deviceString isEqualToString:@"iPad2,1"]) {
          s = @"iPad 2";
      } else if ([deviceString isEqualToString:@"iPad2,2"]) {
          s = @"iPad 2";
      } else if ([deviceString isEqualToString:@"iPad2,3"]) {
          s = @"iPad 2";
      } else if ([deviceString isEqualToString:@"iPad2,4"]) {
          s = @"iPad 2";
      } else if ([deviceString isEqualToString:@"iPad2,5"]) {
          s = @"iPad Mini";
      } else if ([deviceString isEqualToString:@"iPad2,6"]) {
          s = @"iPad Mini";
      } else if ([deviceString isEqualToString:@"iPad2,7"]) {
          s = @"iPad Mini";
      } else if ([deviceString isEqualToString:@"iPad3,1"]) {
          s = @"iPad 3";
      } else if ([deviceString isEqualToString:@"iPad3,2"]) {
          s = @"iPad 3";
      } else if ([deviceString isEqualToString:@"iPad3,3"]) {
          s = @"iPad 3";
      } else if ([deviceString isEqualToString:@"iPad3,4"]) {
          s = @"iPad 4";
      } else if ([deviceString isEqualToString:@"iPad3,5"]) {
          s = @"iPad 4";
      } else if ([deviceString isEqualToString:@"iPad3,6"]) {
          s = @"iPad 4";
      } else if ([deviceString isEqualToString:@"iPad4,1"]) {
          s = @"iPad Air";
      } else if ([deviceString isEqualToString:@"iPad4,2"]) {
          s = @"iPad Air";
      } else if ([deviceString isEqualToString:@"iPad4,4"]) {
          s = @"iPad Mini 2";
      } else if ([deviceString isEqualToString:@"iPad4,5"]) {
          s = @"iPad Mini 2";
      } else if ([deviceString isEqualToString:@"iPad4,6"]) {
          s = @"iPad Mini 2";
      } else if ([deviceString isEqualToString:@"iPad4,7"]) {
          s = @"iPad Mini 3";
      } else if ([deviceString isEqualToString:@"iPad4,8"]) {
          s = @"iPad Mini 3";
      } else if ([deviceString isEqualToString:@"iPad4,9"]) {
          s = @"iPad Mini 3";
      } else if ([deviceString isEqualToString:@"iPad5,1"]) {
          s = @"iPad Mini 4";
      } else if ([deviceString isEqualToString:@"iPad5,2"]) {
          s = @"iPad Mini 4";
      } else if ([deviceString isEqualToString:@"iPad5,3"]) {
          s = @"iPad Air 2";
      } else if ([deviceString isEqualToString:@"iPad5,4"]) {
          s = @"iPad Air 2";
      } else if ([deviceString isEqualToString:@"iPad6,3"]) {
          s = @"iPad Pro 9.7";
      } else if ([deviceString isEqualToString:@"iPad6,4"]) {
          s = @"iPad Pro 9.7";
      } else if ([deviceString isEqualToString:@"iPad6,7"]) {
          s = @"iPad Pro 12.9";
      } else if ([deviceString isEqualToString:@"iPad6,8"]) {
          s = @"iPad Pro 12.9";
      } else if ([deviceString isEqualToString:@"iPad6,11"]) {
          s = @"iPad 5";
      } else if ([deviceString isEqualToString:@"iPad6,12"]) {
          s = @"iPad 5";
      } else if ([deviceString isEqualToString:@"iPad7,1"]) {
          s = @"iPad Pro 12.9";
      } else if ([deviceString isEqualToString:@"iPad7,2"]) {
          s = @"iPad Pro 12.9";
      } else if ([deviceString isEqualToString:@"iPad7,3"]) {
          s = @"iPad Pro 10.5";
      } else if ([deviceString isEqualToString:@"iPad7,4"]) {
          s = @"iPad Pro 10.5";
      } else if ([deviceString isEqualToString:@"AppleTV2,1"]) {
          s = @"Apple TV 2";
      } else if ([deviceString isEqualToString:@"AppleTV3,1"]) {
          s = @"Apple TV 3";
      } else if ([deviceString isEqualToString:@"AppleTV3,2"]) {
          s = @"Apple TV 3";
      } else if ([deviceString isEqualToString:@"AppleTV5,3"]) {
          s = @"Apple TV 4";
      } else if ([deviceString isEqualToString:@"i386"]) {
          s = @"Simulator";
      } else if ([deviceString isEqualToString:@"x86_64"]) {
          s = @"Simulator";
      } else {
          s = deviceString;
      }
    });
    return s;
}

+ (NSString *)modelName
{
    static NSString *      modelName = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      unsigned long size = 0;
      sysctlbyname("hw.machine", NULL, &size, NULL, 0);
      char *machine = malloc(size);
      sysctlbyname("hw.machine", machine, &size, NULL, 0);
      modelName = [NSString stringWithUTF8String:machine];
    });
    return modelName;
}

+ (NSDictionary *)ipv4Addresses
{
    struct ifaddrs *ifa, *ifa_tmp;
    if (getifaddrs(&ifa) != 0) {
        return nil;
    }

    NSMutableDictionary *result = NSMutableDictionary.new;
    char                 addr[100];
    ifa_tmp = ifa;
    while (ifa_tmp) {
        if (ifa_tmp->ifa_addr && ifa_tmp->ifa_name) {
            switch (ifa_tmp->ifa_addr->sa_family) {
                case AF_INET: {
                    // ipv4
                    struct sockaddr_in *in = (struct sockaddr_in *)ifa_tmp->ifa_addr;
                    addr[0]                = '\0';
                    inet_ntop(AF_INET, &in->sin_addr, addr, sizeof(addr));
                    if (strlen(addr)) {
                        result[[NSString stringWithFormat:@"%s", ifa_tmp->ifa_name]] =
                            [NSString stringWithFormat:@"%s", addr] ?: @"";
                    }
                    break;
                }

                default:
                    break;
            }
        }

        ifa_tmp = ifa_tmp->ifa_next;
    }
    freeifaddrs(ifa);
    ifa     = NULL;
    ifa_tmp = NULL;

    return result.count ? result : nil;
}

+ (NSDictionary *)ipv6Addresses
{
    struct ifaddrs *ifa, *ifa_tmp;
    if (getifaddrs(&ifa) != 0) {
        return nil;
    }

    NSMutableDictionary *result = NSMutableDictionary.new;
    char                 addr[200];
    ifa_tmp = ifa;
    while (ifa_tmp) {
        if (ifa_tmp->ifa_addr && ifa_tmp->ifa_name) {
            switch (ifa_tmp->ifa_addr->sa_family) {
                case AF_INET6: {
                    // ipv6
                    struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)ifa_tmp->ifa_addr;
                    addr[0]                  = '\0';
                    inet_ntop(AF_INET6, &in6->sin6_addr, addr, sizeof(addr));
                    if (strlen(addr)) {
                        result[[NSString stringWithFormat:@"%s", ifa_tmp->ifa_name]] =
                            [NSString stringWithFormat:@"%s", addr] ?: @"";
                    }
                    break;
                }

                default:
                    break;
            }
        }

        ifa_tmp = ifa_tmp->ifa_next;
    }
    freeifaddrs(ifa);
    ifa     = NULL;
    ifa_tmp = NULL;

    return result.count ? result : nil;
}

+ (BOOL)isUsingPorxy
{
    NSDictionary *proxySettings = (__bridge NSDictionary *)(CFNetworkCopySystemProxySettings());
    NSArray *     proxies       = (__bridge NSArray *)(CFNetworkCopyProxiesForURL(
        (__bridge CFURLRef _Nonnull)([NSURL URLWithString:@"https://www.baidu.com"]),
        (__bridge CFDictionaryRef _Nonnull)(proxySettings)));
    NSDictionary *settings      = proxies.firstObject;

    if (settings && ![settings[(__bridge NSString *)kCFProxyTypeKey] isEqualToString:@"kCFProxyTypeNone"]) {
        return true;
    }
    return false;
}

+ (BOOL)isUsingVPN
{
    struct ifaddrs *ifa, *ifa_tmp;
    char            addr[100];

    if (getifaddrs(&ifa) != 0) {
        return false;
    }

    ifa_tmp = ifa;
    while (ifa_tmp) {
        if (ifa_tmp->ifa_addr && ifa_tmp->ifa_name) {
            switch (ifa_tmp->ifa_addr->sa_family) {
                case AF_INET: {
                    // ipv4
                    struct sockaddr_in *in = (struct sockaddr_in *)ifa_tmp->ifa_addr;
                    addr[0]                = '\0';
                    inet_ntop(AF_INET, &in->sin_addr, addr, sizeof(addr));
                    if (strlen(addr)) {
                        // tun/tap openvpn
                        /*
                         Tun/tap interfaces are software-only interfaces, meaning that they exist only in the kernel
                         and, unlike regular network interfaces, they have no physical hardware component (and so
                         there's no physical "wire" connected to them). You can think of a tun/tap interface as a
                         regular network interface that, when the kernel decides that the moment has come to send data
                         "on the wire", instead sends data to some userspace program that is attached to the interface
                         (using a specific procedure, see below). When the program attaches to the tun/tap interface, it
                         gets a special file descriptor, reading from which gives it the data that the interface is
                         sending out. In a similar fashion, the program can write to this special descriptor, and the
                         data (which must be properly formatted, as we'll see) will appear as input to the tun/tap
                         interface. To the kernel, it would look like the tun/tap interface is receiving data "from the
                         wire". The difference between a tap interface and a tun interface is that a tap interface
                         outputs (and must be given) full ethernet frames, while a tun interface outputs (and must be
                         given) raw IP packets (and no ethernet headers are added by the kernel). Whether an interface
                         functions like a tun interface or like a tap interface is specified with a flag when the
                         interface is created.

                         The interface can be transient, meaning that it's created, used and destroyed by the same
                         program; when the program terminates, even if it doesn't explicitly destroy the interface, the
                         interfaces ceases to exist. Another option (the one I prefer) is to make the interface
                         persistent; in this case, it is created using a dedicated utility (like tunctl or openvpn
                         --mktun), and then normal programs can attach to it; when they do so, they must connect using
                         the same type (tun or tap) used to originally create the interface, otherwise they will not be
                         able to attach. We'll see how that is done in the code.

                         Once a tun/tap interface is in place, it can be used just like any other interface, meaning
                         that IP addresses can be assigned, its traffic can be analyzed, firewall rules can be created,
                         routes pointing to it can be established, etc.
                         */
                        // ppp pptp
                        if (strstr(ifa_tmp->ifa_name, "tap") != NULL || strstr(ifa_tmp->ifa_name, "tun") != NULL ||
                            strstr(ifa_tmp->ifa_name, "ppp") != NULL) {
                            freeifaddrs(ifa);
                            ifa     = NULL;
                            ifa_tmp = NULL;

                            return true;
                        }
                    }
                    break;
                }

                case AF_INET6: {
                    // ipv6
                    //                    struct sockaddr_in6 *in6 = (struct sockaddr_in6*) ifa_tmp->ifa_addr;
                    //                    inet_ntop(AF_INET6, &in6->sin6_addr, addr, sizeof(addr));
                    break;
                }

                default:
                    break;
            }
        }

        ifa_tmp = ifa_tmp->ifa_next;
    }
    freeifaddrs(ifa);
    ifa     = NULL;
    ifa_tmp = NULL;

    return false;
}

+ (BOOL)isPhone4Like
{
    static BOOL            r = false;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      r = UIScreen.screenHeight <= 480;
    });
    return r;
}

+ (BOOL)isPhoneX
{
    static BOOL r = false;
    if (@available(iOS 11.0, *)) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
          if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
              // 2436x1125
              CGSize    s = UIScreen.mainScreen.nativeBounds.size;
              NSInteger w = (NSInteger)s.width;
              NSInteger h = (NSInteger)s.height;
              r           = w == 1125 && h == 2436;
          }
        });
    }
    return r;
}

+ (BOOL)isPhoneXLike {
    static BOOL r = false;
    if (@available(iOS 11.0, *)) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            if (UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
                r = UIApplication.sharedApplication.delegate.window.safeAreaInsets.bottom > 0.f;
            }
        });
    }
    return r;

}



+ (long)diskSpace
{
    static long            s = 0;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      s = (long)[[[NSFileManager.defaultManager attributesOfFileSystemForPath:NSHomeDirectory() error:NULL]
          objectForKey:NSFileSystemSize] longLongValue];
    });
    return s;
}

+ (NSString *)diskSpaceStr
{
    static NSString *      s = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      long long space = [[[NSFileManager.defaultManager attributesOfFileSystemForPath:NSHomeDirectory() error:NULL]
          objectForKey:NSFileSystemSize] longLongValue];
      if (space > 1024 * 1024 * 1024) {
          // GB
          s = [NSString stringWithFormat:@"%lld.%ld G", space / (1024 * 1024 * 1024),
                                         (long)(((space % (1024 * 1024 * 1024)) / (1024 * 1024 * 1024.0)) * 100)];
      } else if (space > 1024 * 1024) {
          // M
          s = [NSString stringWithFormat:@"%lld.%ld M", space / (1024 * 1024),
                                         (long)(((space % (1024 * 1024)) / (1024 * 1024.0)) * 100)];
      } else if (space > 1024) {
          // KB
          s = [NSString stringWithFormat:@"%lld.%ld KB", space / 1024, (long)((space % 1024) / 1024.0 * 100)];
      } else {
          s = [NSString stringWithFormat:@"%lld B", space];
      }
    });
    return s ?: @"---";
}

+ (long)freeDiskSpace
{
    return (long)[[[NSFileManager.defaultManager attributesOfFileSystemForPath:NSHomeDirectory() error:NULL]
        objectForKey:NSFileSystemFreeSize] longLongValue];
}

+ (NSString *)freeDiskSpaceStr
{
    NSString *s     = @"---";
    long long space = [[[NSFileManager.defaultManager attributesOfFileSystemForPath:NSHomeDirectory() error:NULL]
        objectForKey:NSFileSystemFreeSize] longLongValue];
    if (space > 1024 * 1024 * 1024) {
        // GB
        s = [NSString stringWithFormat:@"%lld.%ld G", space / (1024 * 1024 * 1024),
                                       (long)(((space % (1024 * 1024 * 1024)) / (1024 * 1024 * 1024.0)) * 100)];
    } else if (space > 1024 * 1024) {
        // M
        s = [NSString stringWithFormat:@"%lld.%ld M", space / (1024 * 1024),
                                       (long)(((space % (1024 * 1024)) / (1024 * 1024.0)) * 100)];
    } else if (space > 1024) {
        // KB
        s = [NSString stringWithFormat:@"%lld.%ld KB", space / 1024, (long)((space % 1024) / 1024.0 * 100)];
    } else {
        s = [NSString stringWithFormat:@"%lld B", space];
    }
    return s;
}

@end
